import Axios, {
  type AxiosInstance,
  type AxiosRequestConfig,
  type CustomParamsSerializer
} from "axios";
import type {
  PureHttpError,
  RequestMethods,
  PureHttpResponse,
  PureHttpRequestConfig
} from "./types.d";
import { h } from "vue";
import { stringify } from "qs";
import NProgress from "../progress";
import { getToken, formatToken } from "@/utils/auth";
import { useUserStoreHook } from "@/store/modules/user";
import { message } from "@/utils/message";

export interface StreamDownloadOptions {
  method?: "GET" | "POST" | "PUT" | "DELETE" | string;
  /** URL 查询参数 */
  params?: Record<string, string | number | boolean>;
  /** 请求体（JSON），仅当 method != 'GET' 时使用 */
  data?: any;
}

// 相关配置请参考：www.axios-js.com/zh-cn/docs/#axios-request-config-1
const defaultConfig: AxiosRequestConfig = {
  // 请求超时时间
  timeout: 60000,
  headers: {
    Accept: "application/json, text/plain, */*",
    "Content-Type": "application/json",
    "X-Requested-With": "XMLHttpRequest"
  },
  // 数组格式参数序列化（https://github.com/axios/axios/issues/5142）
  paramsSerializer: {
    serialize: stringify as unknown as CustomParamsSerializer
  }
};

class PureHttp {
  constructor() {
    this.httpInterceptorsRequest();
    this.httpInterceptorsResponse();
  }

  /** `token`过期后，暂存待执行的请求 */
  private static requests = [];

  /** 防止重复刷新`token` */
  private static isRefreshing = false;

  /** 初始化配置对象 */
  private static initConfig: PureHttpRequestConfig = {};

  /** 保存当前`Axios`实例对象 */
  private static axiosInstance: AxiosInstance = Axios.create(defaultConfig);

  /** 重连原始请求 */
  private static retryOriginalRequest(config: PureHttpRequestConfig) {
    return new Promise(resolve => {
      PureHttp.requests.push((token: string) => {
        config.headers["Authorization"] = formatToken(token);
        resolve(config);
      });
    });
  }

  /** 请求拦截 */
  private httpInterceptorsRequest(): void {
    PureHttp.axiosInstance.interceptors.request.use(
      async (config: PureHttpRequestConfig): Promise<any> => {
        // 开启进度条动画
        NProgress.start();
        // 优先判断post/get等方法是否传入回调，否则执行初始化设置等回调
        if (typeof config.beforeRequestCallback === "function") {
          config.beforeRequestCallback(config);
          return config;
        }
        if (PureHttp.initConfig.beforeRequestCallback) {
          PureHttp.initConfig.beforeRequestCallback(config);
          return config;
        }
        /** 请求白名单，放置一些不需要`token`的接口（通过设置请求白名单，防止`token`过期后再请求造成的死循环问题） */
        const whiteList = ["/refresh-token", "/api/auth/login"];
        return whiteList.some(url => config.url.endsWith(url))
          ? config
          : new Promise(resolve => {
              const data = getToken();
              if (data) {
                const now = new Date().getTime();
                const expired = parseInt(data?.expires) - now <= 0;
                if (expired) {
                  if (!PureHttp.isRefreshing) {
                    PureHttp.isRefreshing = true;
                    // token过期刷新
                    useUserStoreHook()
                      .handRefreshToken({ refreshToken: data.refreshToken })
                      .then(res => {
                        const token = res.data.token;
                        config.headers["Authorization"] = formatToken(token);
                        PureHttp.requests.forEach(cb => cb(token));
                        PureHttp.requests = [];
                      })
                      .finally(() => {
                        PureHttp.isRefreshing = false;
                      });
                  }
                  resolve(PureHttp.retryOriginalRequest(config));
                } else {
                  config.headers["Authorization"] = formatToken(data.token);
                  resolve(config);
                }
              } else {
                resolve(config);
              }
            });
      },
      error => {
        return Promise.reject(error);
      }
    );
  }

  /** 响应拦截 */
  private httpInterceptorsResponse(): void {
    const instance = PureHttp.axiosInstance;
    instance.interceptors.response.use(
      (response: PureHttpResponse) => {
        const $config = response.config;
        // 关闭进度条动画
        NProgress.done();
        // 如果响应类型是 blob，则直接返回响应，不做 JSON success 校验
        if ($config?.responseType === "blob") {
          return response;
        }
        if (!response?.data?.success) {
          if (!$config?.hideMessage) {
            message(
              h(
                "p",
                {
                  class:
                    "flex flex-col items-center justify-center text-center gap-1 text-sm"
                },
                [
                  response.data?.message
                    ? h("span", null, response.data?.message)
                    : null,
                  response.data?.detailMessage
                    ? h("span", null, response.data?.detailMessage)
                    : null,
                  !response.data?.message && !response.data?.detailMessage
                    ? h("span", null, "操作失败")
                    : null
                ]
              ),
              {
                dangerouslyUseHTMLString: true,
                type: "error",
                center: true
              }
            );
          }
          return Promise.reject(response.data);
        }
        // 优先判断post/get等方法是否传入回调，否则执行初始化设置等回调
        if (typeof $config.beforeResponseCallback === "function") {
          $config.beforeResponseCallback(response);
          return response.data;
        }
        if (PureHttp.initConfig.beforeResponseCallback) {
          PureHttp.initConfig.beforeResponseCallback(response);
          return response.data;
        }
        return response.data;
      },
      (error: any) => {
        const $error = error;
        $error.isCancelRequest = Axios.isCancel($error);
        if (Axios.isCancel($error) || $error?.code === "ERR_CANCELED") {
          // 标记为取消请求，静默处理：不弹错误、不处理 401 等
          NProgress.done();
          return Promise.reject($error);
        }
        if ($error?.response?.status === 401) {
          useUserStoreHook().loginOutCallBack();
          return Promise.reject(error);
        }
        if (
          !$error?.response?.data?.success &&
          !$error?.response?.config?.hideMessage &&
          !$error?.config?.hideMessage
        ) {
          message(
            h(
              "p",
              {
                class:
                  "flex flex-col items-center justify-center text-center gap-1 text-sm"
              },
              [
                $error?.response?.data?.message
                  ? h("span", null, $error?.response?.data?.message)
                  : null,
                $error?.response?.data?.detailMessage
                  ? h("span", null, $error?.response?.data?.detailMessage)
                  : null,
                !$error?.response?.data?.message &&
                !$error?.response?.data?.detailMessage
                  ? h("span", null, "网络问题，请稍后再试")
                  : null
              ]
            ),
            {
              dangerouslyUseHTMLString: true,
              type: "error",
              center: true
            }
          );
        }
        // 关闭进度条动画
        NProgress.done();

        // 所有的响应异常 区分来源为取消请求/非取消请求
        return Promise.reject($error);
      }
    );
  }

  /** 通用请求工具函数 */
  public request<T>(
    method: RequestMethods,
    url: string,
    param?: AxiosRequestConfig,
    axiosConfig?: PureHttpRequestConfig
  ): Promise<T> {
    const config = {
      method,
      url,
      ...param,
      ...axiosConfig
    } as PureHttpRequestConfig;

    // 单独处理自定义请求/响应回调
    return new Promise((resolve, reject) => {
      PureHttp.axiosInstance
        .request(config)
        .then((response: undefined) => {
          resolve(response);
        })
        .catch(error => {
          reject(error);
        });
    });
  }

  /** 单独抽离的`post`工具函数 */
  public post<T, P>(
    url: string,
    params?: AxiosRequestConfig<P>,
    config?: PureHttpRequestConfig
  ): Promise<T> {
    return this.request<T>("post", url, params, config);
  }

  /** 单独抽离的`get`工具函数 */
  public get<T, P>(
    url: string,
    params?: AxiosRequestConfig<P>,
    config?: PureHttpRequestConfig
  ): Promise<T> {
    return this.request<T>("get", url, params, config);
  }

  public async streamDownload(
    url: string,
    options: StreamDownloadOptions = {}
  ): Promise<{
    reader: ReadableStreamDefaultReader<Uint8Array>;
    total?: number;
    disposition?: string;
    contentType?: string;
  }> {
    // 1. 处理查询参数
    let fullUrl = url;
    if (options.params) {
      const qs = Object.entries(options.params)
        .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
        .join("&");
      fullUrl += fullUrl.includes("?") ? `&${qs}` : `?${qs}`;
    }

    // 2. 构造 Headers（含 Authorization）
    const tk = getToken()?.token;
    const headers = new Headers({ Accept: "application/octet-stream" });
    if (tk) headers.set("Authorization", formatToken(tk));
    if (options.method && options.method !== "GET") {
      headers.set("Content-Type", "application/json");
    }

    try {
      // 3. 发起 fetch
      const res = await fetch(fullUrl, {
        method: options.method || "GET",
        headers,
        credentials: "include",
        body:
          options.method && options.method !== "GET"
            ? JSON.stringify(options.data)
            : undefined
      });
      if (!res.ok) throw new Error(`流式下载失败：HTTP ${res.status}`);
      if (!res.body) throw new Error("当前环境不支持 ReadableStream");
      // 拿 header
      const disposition = res.headers.get("Content-Disposition") || undefined;
      // 4. 读取总长度（如果有的话）

      const total = Number(res.headers.get("Content-Length") ?? 0) || undefined;

      const contentType = res.headers.get("Content-Type") || undefined;

      // 5. 返回 reader
      return { reader: res.body!.getReader(), total, disposition, contentType };
    } catch (e) {
      message("下载失败", {
        type: "error"
      });
    }
  }
}

export const http = new PureHttp();
